SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
14.2 KB · 230 lines tsx
Raw Blame History
1import { ExternalLink } from 'lucide-react';2import type { Metadata } from 'next';3import Link from 'next/link';4import { permanentRedirect } from 'next/navigation';5import { CompareButton } from '@/components/compare/compare-button';6import { CompareTrayBar } from '@/components/compare/compare-tray-bar';7import { RelationsBlock, SourcesTable, TimelineList } from '@/components/entity/blocks';8import { buildMetadata, loadEntity } from '@/components/entity/load';9import { QUANTS } from '@/components/hardware/fit-form';10import { fmtMemory, HW_KIND_LABELS, memoryOptions } from '@/components/hardware/hardware-bits';11import { EstimateBanner, FitBreakdownList, Methodology, SourceTag } from '@/components/intelligence/bits';12import { ViewBeacon } from '@/components/layout/view-beacon';13import { Chip, EntityBadge, Estimated, OpennessBadge, StatusBadge } from '@/components/ui/badges';14import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';15import { EntityLink, QualityMark } from '@/components/ui/entity';16import { KeyValue } from '@/components/ui/key-value';17import { Container, Note, Section } from '@/components/ui/section';18import { EmptyState, Unavailable } from '@/components/ui/unavailable';19import { WatchButton } from '@/components/watchlist/watch-button';20import { api, intel, safe } from '@/lib/api';21import { fmtGb, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';22import { PROSE_KEYS, routes, SITE_NAME, SITE_URL } from '@/lib/site';2324export const revalidate = 600;25type Params = { params: Promise<{ slug: string }>; searchParams: Promise<Record<string, string | undefined>> };26const CONTEXTS = [8192, 32768, 131072];2728export async function generateMetadata({ params }: Params): Promise<Metadata> {29  const { slug } = await params;30  const d = await safe(api.entityOfType('hardware', slug));31  if (!d || d.entity_type !== 'hardware') return { title: 'Hardware', robots: { index: false } };32  const m = buildMetadata(d);33  const a = d.attributes ?? {};34  return { ...m, title: `${d.name} — specs and which AI models fit (estimated)`, description: `${d.name}${typeof a.manufacturer === 'string' ? ` by ${a.manufacturer}` : ''}: ${fmtMemory(a.memory_gb)} memory${num(a.memory_bandwidth_gbs) !== null ? `, ${fmtInt(a.memory_bandwidth_gbs)} GB/s` : ''} as published, with sources — and the models estimated to fit at 4-bit, 8-bit and fp16. ${SITE_NAME}.` };35}3637export default async function HardwarePage({ params, searchParams }: Params) {38  const { slug } = await params;39  const sp = await searchParams;40  const d = await loadEntity('hardware', slug);41  const canonical = routes.entity(d);42  if (canonical !== `/hardware/${encodeURIComponent(slug)}`) permanentRedirect(canonical);43  const a = d.attributes ?? {};44  const quant = QUANTS.some((q) => q.value === sp.quant) ? (sp.quant as string) : '4bit';45  const ctx = num(sp.context);46  const context = ctx !== null && ctx > 0 ? Math.round(ctx) : 8192;47  const memChoice = num(sp.memory_gb);48  const options = memoryOptions(a.memory_gb);49  const fit = await safe(intel.hardwareSlugFit(d.slug, { quant, context, memory_gb: memChoice ?? undefined, limit: 120 }));50  const memoryUsed = num(fit?.inputs?.memory_gb) ?? memChoice ?? options.at(-1) ?? null;51  const self = (patch: { quant?: string; context?: number; memory_gb?: number | null }) => {52    const p = new URLSearchParams();53    const q = patch.quant ?? quant;54    const c = patch.context ?? context;55    const m = patch.memory_gb === undefined ? memChoice : patch.memory_gb;56    if (q !== '4bit') p.set('quant', q);57    if (c !== 8192) p.set('context', String(c));58    if (m !== null && m !== undefined) p.set('memory_gb', String(m));59    const s = p.toString();60    return `${canonical}${s ? `?${s}` : ''}#fit`;61  };62  const specRows = Object.keys(a)63    .filter((k) => !PROSE_KEYS.has(k))64    .map((k) => ({ key: k, raw: a[k] }));65  const items = fit?.items ?? [];66  const ld = {67    '@context': 'https://schema.org',68    '@type': 'Product',69    name: d.name,70    brand: typeof a.manufacturer === 'string' ? { '@type': 'Brand', name: a.manufacturer } : undefined,71    category: typeof a.kind === 'string' ? HW_KIND_LABELS[a.kind] ?? a.kind : 'Hardware',72    description: d.description ?? undefined,73    url: `${SITE_URL}${canonical}`,74    additionalProperty: [75      options.length ? { '@type': 'PropertyValue', name: 'Memory', value: fmtMemory(a.memory_gb) } : null,76      num(a.memory_bandwidth_gbs) !== null ? { '@type': 'PropertyValue', name: 'Memory bandwidth', value: `${fmtInt(a.memory_bandwidth_gbs)} GB/s` } : null,77    ].filter(Boolean),78  };79  const chip = (on: boolean) => `inline-flex h-9 items-center border px-2.5 text-xs font-medium ${on ? 'border-ink bg-ink text-canvas' : 'border-rule text-ink-2 hover:border-rule-strong hover:text-ink'}`;8081  return (82    <Container wide>83      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(ld) }} />84      <ViewBeacon path={canonical} />85      <header className="pb-5 pt-7 md:pt-10">86        <div className="flex flex-wrap items-center gap-2 text-sm text-ink-3">87          <Link href={routes.hardware()} className="hover:text-ink">Hardware</Link> <span aria-hidden>/</span>88          <EntityBadge type={d.entity_type} />89          {typeof a.kind === 'string' && <Chip>{HW_KIND_LABELS[a.kind] ?? a.kind}</Chip>}90          <StatusBadge status={d.status} />91        </div>92        <div className="mt-2 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">93          <div className="min-w-0">94            <h1 className="display text-[28px] md:text-[40px]">{d.name}</h1>95            <p className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-ink-2">96              {typeof a.manufacturer === 'string' && <span>{a.manufacturer}</span>}97              {d.organization && (98                <Link href={routes.entity({ entity_type: 'company', slug: d.organization.slug })} className="hover:text-accent">99                  {d.organization.name}100                </Link>101              )}102              {typeof a.spec_url === 'string' && (103                <a href={a.spec_url} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 hover:text-accent">104                  spec sheet <ExternalLink className="size-3" aria-hidden />105                </a>106              )}107              <QualityMark q={d.quality?.score} label />108            </p>109            <dl className="tnum mt-3 flex flex-wrap gap-x-6 gap-y-1 text-sm">110              <div><dt className="inline text-ink-3">Memory </dt><dd className="inline font-medium text-ink">{fmtMemory(a.memory_gb)}</dd></div>111              <div><dt className="inline text-ink-3">Bandwidth </dt><dd className="inline font-medium text-ink">{num(a.memory_bandwidth_gbs) === null ? '—' : `${fmtInt(a.memory_bandwidth_gbs)} GB/s`}</dd></div>112              <div><dt className="inline text-ink-3">TDP </dt><dd className="inline font-medium text-ink">{num(a.tdp_watts) === null ? '—' : `${fmtInt(a.tdp_watts)} W`}</dd></div>113              {Array.isArray(a.runtimes) && a.runtimes.length > 0 && <div><dt className="inline text-ink-3">Runtimes </dt><dd className="inline font-medium text-ink">{(a.runtimes as string[]).join(', ')}</dd></div>}114            </dl>115            {d.description && <p className="mt-3 max-w-2xl text-[15px] leading-relaxed text-ink-2">{d.description}</p>}116          </div>117          <div className="flex shrink-0 flex-wrap items-center gap-2">118            <CompareButton e={d} />119            <WatchButton e={d} />120            <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}`} className="inline-flex h-9 items-center border border-rule-strong px-2.5 text-sm font-medium text-ink hover:border-accent hover:text-accent">121              Run locally with this device →122            </Link>123          </div>124        </div>125      </header>126127      <Section eyebrow="Specifications" title="As published" lede="Every value opens its evidence (source, tier, observed time)." hairline={false}>128        <KeyValue rows={specRows} provenance={d.provenance} slug={d.slug} entity={{ name: d.name, entity_type: d.entity_type }} />129      </Section>130131      {/* ------------------------------------------------------------------------------------------------- fit */}132      <Section id="fit" eyebrow="What can this run?" title={<span className="inline-flex flex-wrap items-center gap-2">Models estimated to fit <Estimated /></span>} lede="Estimate from each model's parameter count, the chosen quantization and context — never a measurement." action={{ href: `${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&quant=${quant}&context=${context}${memoryUsed ? `&memory_gb=${memoryUsed}` : ''}`, label: 'Open in Run locally' }}>133        <div className="mb-4 flex flex-wrap items-center gap-x-6 gap-y-3 text-xs">134          <div className="flex flex-wrap items-center gap-1.5">135            <span className="eyebrow mr-1">Quantization</span>136            {QUANTS.map((q) => (137              <Link key={q.value} href={self({ quant: q.value })} className={chip(quant === q.value)} aria-current={quant === q.value ? 'true' : undefined}>138                {q.value}139              </Link>140            ))}141          </div>142          <div className="flex flex-wrap items-center gap-1.5">143            <span className="eyebrow mr-1">Context</span>144            {CONTEXTS.map((c) => (145              <Link key={c} href={self({ context: c })} className={chip(context === c)} aria-current={context === c ? 'true' : undefined}>146                {fmtTokens(c)}147              </Link>148            ))}149          </div>150          {options.length > 1 && (151            <div className="flex flex-wrap items-center gap-1.5">152              <span className="eyebrow mr-1">Configuration</span>153              {options.map((m) => (154                <Link key={m} href={self({ memory_gb: m })} className={chip(memoryUsed === m)} aria-current={memoryUsed === m ? 'true' : undefined}>155                  {m} GB156                </Link>157              ))}158            </div>159          )}160        </div>161        {!fit ? (162          <Unavailable what="Fit estimate" reason={options.length ? undefined : 'This device has no sourced memory figure — nothing can be estimated.'} />163        ) : (164          <>165            <EstimateBanner assumptions={fit.assumptions} note={fit.note ?? null} counts={fit.counts} />166            {options.length > 1 && <Note className="mt-2">This device ships in {options.length} memory configurations ({options.join(' / ')} GB). Estimates use {fmtGb(memoryUsed)} — pick another configuration above. Multiple units: use <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&gpu_count=2`} className="link">Run locally</Link> (memories are summed; interconnect is not modelled).</Note>}167            {fit.runtimes?.length > 0 && <p className="mt-2 text-xs text-ink-3">Runtimes recorded for this device: <span className="mono">{fit.runtimes.join(' · ')}</span></p>}168            {items.length === 0 ? (169              <EmptyState title="No model evaluated" className="mt-4">Models without a sourced parameter count are not estimated.</EmptyState>170            ) : (171              <DataTable scroll compact className="mt-4">172                <thead>173                  <tr>174                    <Th>Model</Th>175                    <Th num>Params</Th>176                    <Th num>Est. memory</Th>177                    <Th num>Headroom</Th>178                    <Th>Fits</Th>179                    <Th>Breakdown</Th>180                    <Th className="w-24" aria-label="Compare" />181                  </tr>182                </thead>183                <tbody>184                  {items.length === 0 && <EmptyRow cols={7} />}185                  {items.map((r) => {186                    const openness = typeof r.model.attributes?.openness === 'string' ? r.model.attributes.openness : null;187                    const head = num(r.headroom_gb);188                    return (189                      <tr key={r.model.id}>190                        <Td primary>191                          <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">192                            <EntityLink e={r.model} />193                            {openness && <OpennessBadge openness={openness} />}194                          </span>195                          {r.model.organization && <span className="block text-xs text-ink-3">{r.model.organization.name}</span>}196                        </Td>197                        <Td num label="Params" className="tnum">{fmtParams(r.parameter_count ?? r.model.attributes?.parameter_count)}</Td>198                        <Td num label="Est. memory" className="tnum">{fmtGb(r.estimated_memory_gb, 1)} <SourceTag source={r.breakdown?.weights_source ?? 'estimated'} /></Td>199                        <Td num label="Headroom" className={r.fits ? 'tnum text-positive' : 'tnum text-danger'}>{head === null ? '—' : `${head >= 0 ? '+' : '−'}${fmtGb(Math.abs(head), 1)}`}</Td>200                        <Td label="Fits" className={r.fits ? 'font-medium text-positive' : 'text-ink-3'}>{r.fits ? '✓ fits' : '✗ too large'}</Td>201                        <Td label="Breakdown" wide><FitBreakdownList fit={r} /></Td>202                        <Td className="text-right"><CompareButton e={r.model} size="sm" /></Td>203                      </tr>204                    );205                  })}206                </tbody>207              </DataTable>208            )}209            {fit.items.length >= 120 && <Note className="mt-2">First 120 models (fitting first). The full list is in <Link href={`${routes.runLocally()}?hardware=${encodeURIComponent(d.slug)}&quant=${quant}&context=${context}`} className="link">Run locally</Link>.</Note>}210            <Methodology text={fit.note ?? fit.assumptions[0]} />211          </>212        )}213      </Section>214215      {d.relations?.length ? (216        <Section eyebrow="Relations" title="In the graph" action={{ href: routes.graph(d.slug), label: 'Explore graph' }}>217          <RelationsBlock relations={d.relations} />218        </Section>219      ) : null}220      <Section eyebrow="Timeline" title="Events" action={{ href: routes.timeline({ entity: d.slug }), label: 'Full timeline' }}>221        <TimelineList events={d.timeline ?? []} slug={d.slug} />222      </Section>223      <Section eyebrow="Sources" title="Where these facts come from">224        <SourcesTable sources={d.sources ?? []} />225      </Section>226      <CompareTrayBar />227    </Container>228  );229}230